package model.graphic;

import controller.Constants;
import java.awt.Point;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Observable;
import java.util.Set;
import java.util.TreeSet;
import model.graphic.objects.Graphical;
import model.graphic.objects.AbstractInPort;
import model.graphic.objects.AbstractOutPort;
import model.graphic.objects.GConnection;
import model.graphic.objects.GBoxNode;
import model.graphic.objects.Attachment;
import model.graphic.objects.GHandleConnection;
import model.graphic.objects.GResizeHandle;
import model.graphic.objects.GNode;
import model.graphic.objects.GTransmitterNode;
import model.graphic.objects.GTrigger;
import model.graphic.objects.GTriggerConnection;
import model.graphic.objects.RectangleGraphical;
import model.graphic.objects.RectangleAttachment;
import view.GUI.LabelReader;
import view.GUI.sidepanels.CustomArgument;

/**
 *
 * @author Jelle, modified by Ben
 */
public class GraphicModel extends Observable implements Serializable {

    private ArrayList<Graphical> objects;
    private ArrayList<Graphical> selection;
    private Point mouseLocation = new Point(0, 0);
    private Constants cons = new Constants();
    private HashMap<String, RectangleGraphical> link;
    private ArrayList<Graphical> allnodes;
    private GTrigger mainTrigger;
    private GBoxNode boxnodeParent;

    public GraphicModel(GBoxNode boxnodeParent) {
        objects = new ArrayList<Graphical>();
        allnodes = new ArrayList<Graphical>();
        selection = new ArrayList<Graphical>();
        link = new HashMap<String, RectangleGraphical>();
        this.boxnodeParent = boxnodeParent;
    }

    public GBoxNode getBoxnodeParent() {
        return boxnodeParent;
    }

    public GTrigger getMainTrigger() {
        return mainTrigger;
    }

    public void setMainTrigger(GTrigger mainTrigger) {
        this.mainTrigger = mainTrigger;
    }

    public boolean isMainTriggerSet() {
        return (mainTrigger != null && this.getAllObjects().contains(mainTrigger));
    }

    public ArrayList<Graphical> getAllNodes() {
        return allnodes;
    }

    public ArrayList<Graphical> getAllObjects() {
        ArrayList<Graphical> all = new ArrayList<Graphical>();
        for (Graphical g1 : getObjects()) {
            if (GBoxNode.class.isAssignableFrom(g1.getClass())) {
                GBoxNode box = (GBoxNode) g1;
                all.addAll(getAllFromBox(box));
            }
        }
        all.addAll(getObjects());
        return all;
    }

    public ArrayList<Graphical> getAllFromBox(GBoxNode box) {
        ArrayList<Graphical> all = new ArrayList<Graphical>();
        for (Graphical g : box.getInternalObjects()) {
            all.add(g);
        }
        for (Graphical g : box.getTransmitterObjects()) {
            all.add(g);
        }
        return all;
    }

    public ArrayList<Graphical> getAllObjectNoInternals(){
        ArrayList<Graphical> all = new ArrayList<Graphical>();
        //own transmitterports
        if(this.getBoxnodeParent() != null){
            all.addAll(this.getBoxnodeParent().getTransmitterObjects());
        }
        //all the nodes and boxnodes
        for (Graphical g1 : getObjects()) {
            if (GBoxNode.class.isAssignableFrom(g1.getClass())) {
                all.addAll(((GBoxNode) g1).getTransmitterObjects());
            }
            all.add(g1);
        }
        return all;
    }

    public ArrayList<Graphical> getObjects(String type) {
        ArrayList<Graphical> set = new ArrayList<Graphical>();
        Class<?> c = null;
        try {
            c = Class.forName(cons.getDirObjects() + type);
        } catch (ClassNotFoundException ex) {
            System.out.println(LabelReader.getInstance().getString("GraphicModelgetObjectsEXCEPTION")+" " + ex.getMessage());
        }
        for (Graphical g : getAllObjects()) {
            if (c.isAssignableFrom(g.getClass())) {
                set.add(g);
            }
        }
        return set;
    }

    public void removeObject(Graphical g) {
        selection.remove(g);
        objects.remove(g);
        allnodes.remove(g);
        if (RectangleGraphical.class.isAssignableFrom(g.getClass())) {
            RectangleGraphical rg = (RectangleGraphical) g;
            link.remove(rg.getId());
        }
        setChanged();
    }

    public void removeDefObject(Graphical g) {
        boolean found = false;
        if (!objects.remove(g)) {
            for (Graphical gr : getObjects(cons.getBoxNodeString())) {
                GBoxNode bn = (GBoxNode) gr;
                ArrayList<Graphical> overloop = new ArrayList<Graphical>();
                overloop.addAll(bn.getInternalObjects());
                for (Graphical gb : overloop) {
                    if (gb.equals(g)) {
                        found = true;
                        bn.removeInternalObject(g);
                    }
                }
                overloop.clear();
                overloop.addAll(bn.getTransmitterObjects());
                if (!found) {
                    for (Graphical gb : overloop) {
                        if (gb.equals(g)) {
                            found = true;
                            bn.removeTransmitterObject(g);
                        }
                    }
                }
            }
        }
        if (RectangleGraphical.class.isAssignableFrom(g.getClass())) {
            RectangleGraphical rg = (RectangleGraphical) g;
            link.remove(rg.getId());
        }
        setChanged();
    }

    public void addObject(Graphical g) {
        getObjects().add(g);
        if (GNode.class.isAssignableFrom(g.getClass())) {
            setSelected(g);
        }
        if (GNode.class.isAssignableFrom(g.getClass()) || GBoxNode.class.isAssignableFrom(g.getClass())) {
            allnodes.add(g);
        }
        if (RectangleGraphical.class.isAssignableFrom(g.getClass())) {
            RectangleGraphical rg = (RectangleGraphical) g;
            link.put(rg.getId(), rg);
        }
        setChanged();
        notifyObservers(new CustomArgument("tabRight", 1));
    }

    public void editNode(RectangleGraphical node, String value, String w) {
        for (Graphical ni : getObjects(cons.getRectangleGraphicalString())) {
            RectangleGraphical n = (RectangleGraphical) ni;
            if (n.equals(node)) {
                if (w.equals(cons.getNameString())) {
                    n.setName(value);
                } else if (w.equals(cons.getTypeString())) {
                    n.setType(value);
                } else if (w.equals(cons.getValueString())) {
                    GNode nn = (GNode) n;
                    nn.setValue(value);
                }
            }
        }
        setChanged();
        notifyObservers(null);
    }

    public void setModelChanged() {
        setChanged();
        notifyObservers();
    }

    public void removeAll() {
        objects.clear();
        allnodes.clear();
        selection.clear();
        link.clear();
        setChanged();
    }

    public Set<Graphical> getAttachedObjects(Graphical base, String type) {
        Set<Graphical> setb = new TreeSet<Graphical>(new GraphicalComparator());
        try {
            Iterator<Graphical> it;
            if (RectangleGraphical.class.isAssignableFrom(base.getClass())) {
                if (type.startsWith("G")) {
                    Class<?> c = null;
                    try {
                        c = Class.forName(cons.getDirObjects() + type);
                    } catch (ClassNotFoundException ex) {
                        System.out.println(LabelReader.getInstance().getString("GraphicModelgetattachedObjectsEXCEPTION") + " " + ex.getMessage());
                    }
                    for (Graphical g : getAllObjects()) {
                        if (c.isAssignableFrom(g.getClass())) {
                            Attachment h = (Attachment) g;
                            if (h.getBase().equals(base)) {
                                setb.add(g);
                            }
                        }
                    }
                } else {
                    if (type.equals(cons.getAllString())) {
                        it = getAllObjects().iterator();
                        while (it.hasNext()) {
                            Graphical g = it.next();
                            if (Attachment.class.isAssignableFrom(g.getClass())) {
                                Attachment h = (Attachment) g;
                                if (h.getBase().equals(base)) {
                                    setb.add(g);
                                }
                            }
                        }
                    } else if (type.equals(cons.getOutportsString())) {
                        it = getObjects(cons.getOutportsString()).iterator();
                        while (it.hasNext()) {
                            Graphical g = it.next();
                            if (base.equals(((AbstractOutPort) g).getBase())) {
                                setb.add(g);
                            }
                        }
                    } else if (type.equals(cons.getInportsString())) {
                        it = getObjects(cons.getInportsString()).iterator();
                        while (it.hasNext()) {
                            Graphical g = it.next();
                            if (base.equals(((AbstractInPort) g).getBase())) {
                                setb.add(g);
                            }
                        }
                    } else if (type.equals(cons.getPortsString())) {
                        it = getObjects(cons.getPortsString()).iterator();
                        while (it.hasNext()) {
                            Graphical g = it.next();
                            if (base.equals(((Attachment) g).getBase())) {
                                setb.add(g);
                            }
                        }
                    }
                }

            } else if (RectangleAttachment.class.isAssignableFrom(base.getClass().getSuperclass())) {
                it = getObjects(cons.getAttachmentString()).iterator();
                while (it.hasNext()) {
                    Graphical g = it.next();
                    if (base.equals(((Attachment) g).getBase())) {
                        setb.add(g);
                    }
                }
            }
        } catch (Exception ex) {
            System.out.println(LabelReader.getInstance().getString("GraphicModelgetattachedObjectsEXCEPTION") + " " + ex.getMessage());
        }
        return setb;
    }

    public ArrayList<Attachment> getAttachedAllNode(RectangleGraphical node) {
        ArrayList<Attachment> setb = new ArrayList<Attachment>();
        for (Graphical g : getAttachedObjects(node, Constants.getPortsString())) {
            setb.add((Attachment) g);
            for (Graphical g2 : getAttachedObjects(g, Constants.getAllString())) {
                setb.add((Attachment) g2);
            }
        }
        return setb;
    }

    public GResizeHandle getAttachedResize(Graphical base) {
        GResizeHandle res = null;
        Iterator it = getAllObjects().iterator();
        while(it.hasNext() && res == null){
            Graphical g = (Graphical)it.next();
            if (GResizeHandle.class.isAssignableFrom(g.getClass())) {
                if (((GResizeHandle) g).getBase().equals(base)) {
                    res = (GResizeHandle) g;
                }
            }
        }
        return res;
    }

    public ArrayList<GTransmitterNode> getAttachedTransmitterNodes(GBoxNode base) {
        ArrayList<GTransmitterNode> setb = new ArrayList<GTransmitterNode>();
        Iterator<Graphical> it = getObjects(cons.getTransmitterNodeString()).iterator();
        while (it.hasNext()) {
            GTransmitterNode n = (GTransmitterNode) it.next();
            if (n.getBase().equals(base)) {
                setb.add(n);
            }
        }
        return setb;
    }

    public ArrayList<GConnection> getConnectionFromNode(GNode rg) {
        ArrayList<GConnection> conns = new ArrayList<GConnection>();
        for (Graphical g : getObjects(cons.getConnectionString())) {
            GConnection c = (GConnection) g;
            for (Attachment h : getAttachedAllNode(rg)) {
                if (GHandleConnection.class.isAssignableFrom(c.getClass())) {
                    GHandleConnection hc = (GHandleConnection) c;
                    if (h.equals(hc.getInput()) || h.equals(hc.getOutput())) {
                        conns.add(hc);
                    }
                } else if (GTriggerConnection.class.isAssignableFrom(c.getClass())) {
                    GTriggerConnection tc = (GTriggerConnection) c;
                    if (h.equals(tc.getTrigger()) || h.equals(tc.getDispatcher())) {
                        conns.add(tc);
                    }
                }
            }
        }
        return conns;
    }

    public void setGraphicalSelected(Graphical g, boolean value) {
        g.setSelected(value);
        if (value) {
            addToSelection(g);
        } else {
            removeFromSelection(g);
        }
        setChanged();
        notifyObservers(g);
    }

    /**
     * @param h
     * @param value
     */
    public void setGraphicalHighLighted(Graphical h, boolean value) {
        h.setHighlighted(value);
        setChanged();
        notifyObservers();
    }

    /**
     * @param h
     * @param value
     */
    public void setGraphicalVisible(Graphical h, boolean visible) {
        h.setVisible(visible);
        setChanged();
        notifyObservers();
    }

    /**
     * @param handlesSelection the handlesSelection to set
     */
    public void addToSelection(Graphical g) {
        if (!selection.contains(g)) {
            selection.add(g);
            setGraphicalHighLighted(g, true);
        }
        setChanged();
        notifyObservers(null);
    }

    public void removeFromSelection(Graphical g) {
        selection.remove(g);
        setGraphicalHighLighted(g, false);
        setChanged();
        notifyObservers();
    }

    /**
     * @return the handlesSelection
     */
    public ArrayList<Graphical> getSelection(String s) {
        ArrayList<Graphical> setb = new ArrayList<Graphical>();
        try {
            if (s.equals(cons.getAllString())) {
                for (Graphical g : getSelection()) {
                    setb.add(g);
                }
            } else if (s.equals(cons.getAttachmentString())) {
                for (Graphical g : getSelection()) {
                    if (AbstractInPort.class.isAssignableFrom(g.getClass()) || AbstractOutPort.class.isAssignableFrom(g.getClass())) {
                        setb.add(g);
                    }
                }
            } else if (s.equals(cons.getNodeString())) {
                for (Graphical g : getSelection()) {
                    if (RectangleGraphical.class.isAssignableFrom(g.getClass())) {
                        setb.add(g);
                    }
                }
            }
        } catch (Exception ex) {
            System.out.println("GraphicModel getSelection() " + ex.getMessage());
        }
        return setb;
    }

    public Graphical getSelected() {
        if (getSelection().size() > 0) {
            return selection.get(getSelection().size() - 1);
        } else {
            return null;
        }
    }

    public void setSelected(Graphical g) {
        clearSelection();
        selection.add(g);
        setChanged();
        notifyObservers(new CustomArgument("tabRight", 0));
    }

    /**
     * @param handlesSelection the handlesSelection to set
     */
    public void clearSelection() {
        selection.clear();
        for (Graphical g : getAllObjects()) {
            g.setHighlighted(false);
            g.setSelected(false);
        }
        setChanged();
        notifyObservers(new CustomArgument("tabRight", 1));
    }

    /**
     * @param selection the selection to set
     */
    public void setSelection(ArrayList<Graphical> selection) {
        this.selection = selection;
    }

    /**
     * @return the mouseLocation
     */
    public Point getMouseLocation() {
        return mouseLocation;
    }

    /**
     * @param mouseLocation the mouseLocation to set
     */
    public void setMouseLocation(Point mouseLocation) {
        this.mouseLocation = mouseLocation;
        setChanged();
        notifyObservers(mouseLocation);
    }

    /**
     * @return the objects
     */
    public ArrayList<Graphical> getObjects() {
        return objects;
    }

    /**
     * @return the selection
     */
    public ArrayList<Graphical> getSelection() {
        return selection;
    }

    /**
     * @return the link
     */
    public HashMap<String, RectangleGraphical> getLink() {
        return link;
    }

    /**
     * @param objects the objects to set
     */
    public void setObjects(ArrayList<Graphical> objects) {
        this.objects = objects;
    }

    /**
     * @param link the link to set
     */
    public void setLink(HashMap<String, RectangleGraphical> link) {
        this.link = link;
    }
}